#!/usr/bin/env python

"""Unit Tests for fmath.py"""

import unittest
import flamingo.fmath as fmath
import math
import pickle
from numbers import Number

class TestPow(unittest.TestCase):
    def setUp(self):
        self.data = [2, 3, 4, 7, 17, 710, 483, 60495, 712345]
        self.bad = ["hello world!", [1, 2, 3], {'foo': 'bar'},
                    fmath.Vector([1, 1])]

    def testClosestIsNumber(self):
        for d in self.data:
            self.assertTrue(isinstance(fmath.closest_pow2(d),
                                       Number))

    def testClosestIsPow2(self):
        for d in self.data:
            result = fmath.closest_pow2(d)
            self.assertEqual(math.log(result, 2) % 1, 0.0)

    def testClosestIsOne(self):
        for d in self.data:
            self.assertTrue(fmath.closest_pow2(d) >= 1)

    def testClosestIsClosest(self):
        for d in self.data:
            less = math.pow(2, math.ceil(math.log(d)/math.log(2.0)))
            greater = math.pow(2, math.floor(math.log(d)/math.log(2.0)))
            lessd = d - less
            greaterd = greater - d

            if lessd < greaterd:
                self.assertEqual(fmath.closest_pow2(d), greater)
            else:
                self.assertEqual(fmath.closest_pow2(d), less)

    def testClosestBad(self):
        for b in self.bad:
            self.assertRaises(TypeError,
                              fmath.closest_pow2, b)

    def testClosestNeg(self):
        for d in self.data:
            self.assertRaises(ValueError, fmath.closest_pow2, -d)

    def testNextIsNumber(self):
        for d in self.data:
            self.assertTrue(isinstance(fmath.next_pow2(d),
                                       Number))

    def testNextIsPow2(self):
        for d in self.data:
            result = fmath.next_pow2(d)
            self.assertEqual(math.log(result, 2) % 1, 0.0)

    def testNextIsOne(self):
        for d in self.data:
            self.assertTrue(fmath.next_pow2(d) >= 1)

    def testNextGreater(self):
        for d in self.data:
            result = fmath.next_pow2(d)
            self.assertTrue(result >= d)

    def testNextBad(self):
        for b in self.bad:
            self.assertRaises(TypeError,
                              fmath.next_pow2, b)

    def testNextNeg(self):
        for d in self.data:
            self.assertRaises(ValueError, fmath.next_pow2, -d)

    def testPrevIsNumber(self):
        for d in self.data:
            self.assertTrue(isinstance(fmath.prev_pow2(d),
                                       Number))

    def testPrevIsPow2(self):
        for d in self.data:
            result = fmath.prev_pow2(d)
            self.assertEqual(math.log(result, 2) % 1, 0.0)

    def testPrevIsOne(self):
        for d in self.data:
            self.assertTrue(fmath.prev_pow2(d) >= 1)

    def testPrevLess(self):
        for d in self.data:
            result = fmath.prev_pow2(d)
            if result is None:
                self.assertTrue(False)
            self.assertTrue(result <= d)

    def testPrevBad(self):
        for b in self.bad:
            self.assertRaises(TypeError,
                              fmath.prev_pow2, b)

    def testPrevNeg(self):
        for d in self.data:
            self.assertRaises(ValueError, fmath.prev_pow2, -d)


class TestDistance(unittest.TestCase):
    def setUp(self):
        ## data = [[point, point, distance, distance_sq]]
        self.data = [[(0, 0), (0, 1), 1, 1],
                     [(2.5, 2),(4.76, 3.1), 2.5134, 6.3176],
                     [(10, 16),(100, 160), 169.8116, 28836],
                     [fmath.Vector([1.2, 1]), fmath.Vector([16, 1]), 14.8, 219.04],
                     [(0, 0), (0, 0), 0, 0],
                     [fmath.Vector([0, 0]), fmath.Vector([0, 0]), 0, 0]]

        self.long = [[(21, 4, 6, 4, 3), (14, 16, 71, 723, 5)],
                     [(10, 9, 8, 7, 6, 5, 4, 3, 2, 1), (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)],
                     [(1, 2, 3, 4), (5, 4, 3, 2, 1)]]

        self.short = [[(), ()]]

        self.bad = [["hello", "world"],
                    [{'foo': 'bar'}, {'bar': 'foo'}],
                    ["hello world", 35]]

    def testIsNumber(self):
        for d in self.data:
            self.assertTrue(isinstance(fmath.distance(d[0], d[1]),
                                       Number))

    def testIsPos(self):
        for d in self.data:
            self.assertTrue(fmath.distance(d[0], d[1]) >= 0)

    def testValue(self):
        for d in self.data:
            result = fmath.distance(d[0], d[1])
            self.assertEquals(round(result, 2), round(d[2], 2))

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.distance, b[0],b[1])

    ##-------- Sq
    def testIsNumberSq(self):
        for d in self.data:
            self.assertTrue(isinstance(fmath.distance_sq(d[0], d[1]),
                                       Number))

    def testIsPosSq(self):
        for d in self.data:
            self.assertTrue(fmath.distance_sq(d[0], d[1]) >= 0)

    def testValueSq(self):
        for d in self.data:
            result = fmath.distance_sq(d[0], d[1])
            self.assertEquals(round(result, 2), round(d[3], 2))

    def testBadSq(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.distance_sq, b[0],b[1])


class TestMidpoint(unittest.TestCase):
    def setUp(self):
        ## data = [[point, point, midpoint]]
        self.data = [[(0, 0), (0, 1), (0, 0.5)],
                     [(2.5, 2),(4.76, 3.1), (3.63, 2.55)],
                     [(10, 16),(100, 160), (55, 88)],
                     [fmath.Vector([1.13, 1]), (16, 1), (8.565, 1)],
                     [(0, 0), (0, 0), (0, 0)]]

        self.wrong = [[(), ()],
                      [(2,), (1,)]]

        self.bad = [["hello", "world"],
                    [{'foo': 'bar'}, {'bar': 'foo'}],
                    ["hello world", 35]]

    def testValue(self):
        for d in self.data:
            result = fmath.midpoint(d[0], d[1])
            self.assertEquals(round(result[0]), round(d[2][0]))
            self.assertEquals(round(result[1]), round(d[2][1]))

    def testWrong(self):
        for d in self.wrong:
            self.assertRaises(TypeError,
                              fmath.midpoint, d[0], d[1])

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError,
                              fmath.midpoint, b[0],b[1])


class TestArea(unittest.TestCase):
    def setUp(self):
        ## [[data], area, signed_area]
        self.data = [[ [(1, 1), (-1, 1), (-1, -1), (1, -1)], 4, 4],
                     [ [(1, 1), (1, -1), (-1, -1), (-1, 1)], 4, -4],
                     [ [(0, 15.4), (-64.24, -10.1), (14.4, -10.1)], 1002.66, 1002.66 ],
                     [ [(0, 15.4), (14.4, -10.1), (-64.24, -10.1)], 1002.66, -1002.66 ]]

        self.odata = [[ [(1, 1), (-1, 1), (-1, -1), (1, -1)], True],
                      [ [(1, 1), (1, -1), (-1, -1), (-1, 1)], False],
                      [ [(0, 15.4), (-64.24, -10.1), (14.4, -10.1)], True],
                      [ [(0, 15.4), (14.4, -10.1), (-64.24, -10.1)], False]]

        self.bad = [["meow", "rawrf", "ROAR"],
                    [("meow", "woof"), ("woof", "meow"), ("rawr", "roar")],
                    [{'foo': 'bar'}, {'foo': 'bar'}, {'foo': 'bar'}],
                    [(1,), (2,), (3,)],
                    [(1, 2), (3, 4)],
                    [(1, 2)],
                    []]

    ##-------- Pos Area
    def testIsNumber(self):
        for d in self.data:
            self.assertTrue(isinstance(fmath.area(d[0]), Number))

    def testIsPos(self):
        for d in self.data:
            self.assertTrue(fmath.area(d[0]) > 0)

    def testValue(self):
        for d in self.data:
            result = fmath.area(d[0])
            self.assertEquals(round(result, 2), round(d[1], 2))

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.area, b)

    ##-------- Signed Area
    def testIsNumberS(self):
        for d in self.data:
            self.assertTrue(isinstance(fmath.signed_area(d[0]), Number))

    def testValueS(self):
        for d in self.data:
            result = fmath.signed_area(d[0])
            self.assertEquals(round(result, 2), round(d[2], 2))

    def testBadS(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.signed_area, b)

    ##-------- Orientation
    def testValueO(self):
        for d in self.odata:
            result = fmath.orientation_poly(d[0])
            self.assertEquals(result, d[1])

    def testBadO(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.orientation_poly, b)


class TestNormalizeAngle(unittest.TestCase):
    def setUp(self):
        self.datad = [[45, 45],
                      [360, 0],
                      [400, 40],
                      [4000, 40],
                      [-100, 260]]
        self.datar = [[math.radians(45), math.radians(45)],
                      [math.radians(360), math.radians(0)],
                      [math.radians(400), math.radians(40)],
                      [math.radians(4000), math.radians(40)],
                      [math.radians(-100), math.radians(260)]]
        self.bad = ["meow",
                    [16],
                    {'foo': 'bar'}]

    ##-------- Degress
    def testNumber(self):
        for i in range(0, 1000):
            self.assertTrue(isinstance(fmath.normalize_angle(i),
                                       Number))

    def testIsPos(self):
        for i in range(0, 1000):
            self.assertTrue(fmath.normalize_angle(i) >= 0)

    def testValue(self):
        for d in self.datad:
            self.assertEquals(fmath.normalize_angle(d[0]), d[1])

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError,
                              fmath.normalize_angle, b)

    ##-------- Radians
    def testNumberR(self):
        for i in range(0, 1000):
            self.assertTrue(isinstance(fmath.normalize_angler(i),
                                       Number))

    def testIsPosR(self):
        for i in range(0, 1000):
            self.assertTrue(fmath.normalize_angler(i) >= 0)

    def testValueR(self):
        for d in self.datar:
            self.assertAlmostEquals(fmath.normalize_angler(d[0]), d[1])

    def testBadR(self):
        for b in self.bad:
            self.assertRaises(TypeError,
                              fmath.normalize_angler, b)


class TestNormalizePoly(unittest.TestCase):
    def setUp(self):
        self.p = [fmath.Vector((-1,1)), (-1,-1), [1,-1], (1,1)]

        self.bad = [[1, 2, 3],
                    [{'foo': 'bar'}, "meow!", (1, 2), ["3","4"]],
                    [(1,), (2,), (3,)]]

    def testNormalize(self):
        self.assert_(fmath.normalize_poly(self.p) == self.p)
        p = [fmath.Vector((-1,1)), (-1,-1), [1,-1], (1,1), (-1, 1)]
        self.assert_(fmath.normalize_poly(p) == self.p)
        self.assert_(fmath.normalize_poly(self.p, 1) == p)

        p = [(-1,1), (-1,-1), [1,-1], (1,1), (-1, 1), (-1,-1)]
        self.assert_(fmath.normalize_poly(p) == self.p)
        self.assert_(fmath.normalize_poly(self.p, 2) == p)

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.normalize_poly, b)


class TestPolySimplify(unittest.TestCase):
    def setUp(self):
        self.data = [[(1, 0), (1, 8.12), (19.95, 5), (-1, -5), (-1.25, -5.15)],
                     [[-1, 0], [-1, -4.12], [-16.95, -5], [14, 5], [1.25, 5.15]]]

        self.bad = [["meow", "rawrf", "ROAR"],
                    [("meow", "woof"), ("woof", "meow"), ("rawr", "roar")],
                    [{'foo': 'bar'}, {'foo': 'bar'}, {'foo': 'bar'}]]

    def testReturnsList(self):
        for i in range(8):
            for d in self.data:
                self.assertTrue(isinstance(fmath.simplify_poly(d, i),
                                           list))

    def testReturnsCoords(self):
        for i in range(8):
            for d in self.data:
                result = fmath.simplify_poly(d, i)
                b = True

                for p in result:
                    if not (isinstance(p[0], Number) and isinstance(p[1], Number)):
                        b = b and False
                        break

                self.assertTrue(b)

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.simplify_poly, b, 1)

class TestVector(unittest.TestCase):
    def setUp(self):
        self.v = fmath.Vector((111, 222))

        self.bad = [(8, {'foo': 'bar'}),
                    ("meow!", "imah vector!")]

    def testAccess(self):
        v = self.v.copy()
        self.assert_(v.x == 111 and v.y == 222)
        v.x = 333
        v[1] = 444
        self.assert_(v[0] == 333 and v[1] == 444)

    def testMath(self):
        v = self.v.copy()
        self.assertEqual(v + 1, fmath.Vector((112, 223)))
        self.assertEqual(v - 2, [109,220])
        self.assert_(v * 3 == (333,666))
        self.assert_(v / 2.0 == fmath.Vector((55.5, 111)))
        self.assert_(v / 2 == (55.5, 111))
        self.assert_(v ** fmath.Vector((2,3)) == [12321, 10941048])
        self.assert_(v + [-11, 78] == fmath.Vector((100, 300)))

    def testReverseMath(self):
        v = self.v.copy()
        self.assert_(1 + v == fmath.Vector((112,223)))
        self.assert_(2 - v == [-109,-220])
        self.assert_(3 * v == (333,666))
        self.assert_([111,222] ** fmath.Vector((2,3)) == [12321, 10941048])
        self.assert_(v + fmath.Vector((1,1)) == [112, 223])

    def testInplaceMath(self):
        inplace_vec = fmath.Vector((5, 13))
        inplace_src = fmath.Vector((inplace_vec))
        inplace_vec *= .5
        inplace_vec += .5
        inplace_vec /= 3
        inplace_vec += fmath.Vector((-1, -1))
        alternate = (inplace_src*.5 + .5)/3 + [-1, -1]
        self.assertEquals(inplace_vec, alternate)

    def testUnary(self):
        v = self.v.copy()
        v = -v
        self.assert_(v == [-111,-222])
        v = abs(v)
        self.assert_(v == [111,222])

    def testLength(self):
        v = fmath.Vector((3, 4))
        self.assert_(v.length == 5)
        v.length = 10
        self.assert_(v == [6, 8])


    def testAngles(self):
        v = fmath.Vector((0,3))
        self.assert_(v.angle == 90)
        v.angle = 0
        self.assert_(v == (3, 0))
        v2 = (-3,0)
        self.assert_(v.angle_about(v2) == 0)
        v2 = fmath.Vector(v2)
        self.assert_(v2.angle_about(v) == 180)
        v.angle = 90, (0,0)
        self.assert_(v == [0, 3])

    def testComparison(self):
        int_vec = fmath.Vector((3, -2))
        flt_vec = fmath.Vector((3.0, -2.0))
        zero_vec = fmath.Vector((0, 0))
        self.assert_(int_vec == flt_vec)
        self.assert_(int_vec != zero_vec)
        self.assert_((flt_vec == zero_vec) == False)
        self.assert_((flt_vec != int_vec) == False)
        self.assert_(int_vec == (3, -2))
        self.assert_(int_vec != [0, 0])
        self.assert_(int_vec != 5)

    def testConversion(self):
        self.assert_(list(self.v) == [111, 222] and isinstance(list(self.v), list))
        self.assert_(tuple(self.v) == (111, 222) and isinstance(tuple(self.v), tuple))

    def testPickle(self):
        testvec = fmath.Vector((5, .3))
        testvec_str = pickle.dumps(testvec)
        loaded_vec = pickle.loads(testvec_str)
        self.assertEquals(testvec, loaded_vec)

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.Vector, b)


class TestPolygon(unittest.TestCase):
    def setUp(self):
        self.p = fmath.Polygon([(-1,1), (-1,-1), (1,-1), (1,1)])

        self.bad = [[{'foo': 'bar'}, "meow!", (1, 2), ["3","4"]]]

    def testAccess(self):
        p = self.p.copy()
        self.assert_(p[0] == (-1, 1))
        p[0] = (-5, 5)
        self.assert_(p[0] == (-5, 5) and isinstance(p[0], fmath.Vector))
        self.assert_(p.center == (-1, 1))
        p[1] = -4, 7
        self.assert_(p[1] == (-4, 7))
        p[3] = 16
        self.assert_(p[3] == (16, 16))

    def testComparison(self):
        p = self.p.copy()
        self.assert_(p == [(-1,1), (-1,-1), (1,-1), (1,1)])
        q = fmath.Polygon([(-1,1), (-1,-1), (1,-1), (1,1)])
        self.assert_(p == q)
        q[0] = -5, 5
        self.assert_(p != q)
        self.assert_(q != p)

    def testConversion(self):
        p = self.p.copy()
        self.assert_(list(p) == p and isinstance(list(p), list))
        self.assert_(tuple(p) == p and isinstance(tuple(p), tuple))

    def testAngle(self):
        p = self.p.copy()
        self.assert_(p.angle == 0)
        p.center = 1,0
        self.assert_(p.angle == 0)
        p.center = 0, 10
        self.assert_(p.angle == 90)

    def testOrientation(self):
        p = self.p.copy()
        self.assert_(p.orientation == 0)
        l = list(p)
        p.orientation = 90
        self.assert_(p != l)
        self.assertEquals(p, [(-1.0000000000000002, -1.0),
                              (0.99999999999999978, -1.0000000000000002),
                              (1.0000000000000002, 1.0), (-1.0, 1.0000000000000002)])

    def testCollision(self):
        p1 = self.p.copy()
        p2 = [(-0.5,0.5), (-0.5,-0.5), (0.5,-0.5), (0.5,0.5)]
        self.assert_(p1.intersects(p2))
        self.assert_(p1.intersects(p1))
        p1.center = 90, -60
        self.assert_(p1.intersects(p2) == False)
        p1.center = 1.5, 0
        self.assert_(p1.intersects(p2))
        p1.center = 0, -1.5
        self.assert_(p1.intersects(p2))
        p1.center = -1.4, 0
        self.assert_(p1.intersects(p2))

    def testPickle(self):
        testpoly = fmath.Polygon([(-1,1), (-1,-1), (1,-1), (1,1)])
        testpoly_str = pickle.dumps(testpoly)
        loaded_poly = pickle.loads(testpoly_str)
        self.assertEquals(testpoly, loaded_poly)

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.Polygon, b)


class TestRect(unittest.TestCase):
    def setUp(self):
        self.p = fmath.Rect((0, 0), 2, 2)

        self.bad = [[1, 2, 3],
                    [{'foo': 'bar'}, "meow!", (1, 2), ["3","4"]],
                    [(1,), (2,), (3,)]]

    def testAccess(self):
        p = self.p.copy()
        self.assert_(p.center == (0,0))
        self.assert_(p.topleft == (-1, 1))
        self.assert_(p.bottomleft == (-1, -1))
        p.center = 1, 3
        self.assert_(p.center == (1, 3))
        p.topright = 4
        self.assert_(p.topright == (4, 4))
        self.assert_(p.bottomright == (4, 2))
        p[2] = 4, 1
        self.assert_(p.topright == (4, 3))
        self.assert_(p.bottomright == (4, 1))
        p.width = 4
        self.assert_(p.topright == (5, 3))
        p.size = (1, 1)
        self.assert_(p.topleft == (2.5, 2.5))

    def testMath(self):
        pass

    def testComparison(self):
        p = self.p.copy()
        q = [(-1,1), (-1,-1), (1,-1), (1,1)]
        self.assert_(p == q)
        q[0] = (-1, 2)
        self.assert_(p != q)

    def testAngle(self):
        p = self.p.copy()
        self.assert_(p.angle == 0)
        p.center = 1,0
        self.assert_(p.angle == 0)
        p.center = 0, 10
        self.assert_(p.angle == 90)
        p.angle = 0
        self.assert_(p.center == (10, 0))

    def testOrientation(self):
        p = self.p.copy()
        self.assert_(p.orientation == 0)
        l = list(p)
        p.orientation = 90
        self.assert_(p != l)
        self.assertEquals(p, [(-1.0000000000000002, -1.0),
                              (0.99999999999999978, -1.0000000000000002),
                              (1.0000000000000002, 1.0), (-1.0, 1.0000000000000002)])

    def testConversion(self):
        p = self.p.copy()
        self.assert_(list(p) == p and isinstance(list(p), list))
        self.assert_(tuple(p) == p and isinstance(tuple(p), tuple))

    def testPickle(self):
        testrect = fmath.Rect((-1, 1), 2, 2)
        testrect_str = pickle.dumps(testrect)
        loaded_rect = pickle.loads(testrect_str)
        self.assertEquals(testrect, loaded_rect)

    def testBad(self):
        for b in self.bad:
            self.assertRaises(TypeError, fmath.Rect, b)


##-------- Test Suite
testpow             = unittest.TestLoader().loadTestsFromTestCase(TestPow)
testdistance        = unittest.TestLoader().loadTestsFromTestCase(TestDistance)
testmidpoint        = unittest.TestLoader().loadTestsFromTestCase(TestMidpoint)
testarea            = unittest.TestLoader().loadTestsFromTestCase(TestArea)
testnormalized      = unittest.TestLoader().loadTestsFromTestCase(TestNormalizeAngle)
testnormalizepoly   = unittest.TestLoader().loadTestsFromTestCase(TestNormalizePoly)
testsimplify        = unittest.TestLoader().loadTestsFromTestCase(TestPolySimplify)
testvector          = unittest.TestLoader().loadTestsFromTestCase(TestVector)
testpolygon         = unittest.TestLoader().loadTestsFromTestCase(TestPolygon)
testrect            = unittest.TestLoader().loadTestsFromTestCase(TestRect)

testsuite = unittest.TestSuite((testpow, testdistance, testmidpoint, testarea,
                                testnormalized, testnormalizepoly,
                                testsimplify,
                                testvector, testpolygon, testrect))

unittest.TextTestRunner(verbosity=2).run(testsuite)
